import compiler from "./core/compiler";
import type { EUVNode, EUVNodeProperty } from "./interface/euv-node";
import type { EUVOption } from "./interface/euv-option";

let activeEffectFunction: Function | null = null; // using element's render function
export class EUV {
  init: boolean;
  effectLog: boolean;
  appNode?: EUVNode | null;
  mountedElement?: HTMLElement | null;
  mounted: () => void;
  $refs: { [key: string]: ProxyConstructor };
  data: { [key: string]: any };
  watch: { [key: string]: <T>(newValue: T, oldValue: T) => T | void };
  computed: { [key: string]: (...args: any[]) => any };
  methods: { [key: string]: (...args: any[]) => any };
  dataEffectFnMap: Map<any, Set<Function>>; // 数据变化回调

  constructor(
    option: EUVOption = {
      effectLog: false,
      mounted: () => {},
      data: {},
      watch: {},
      computed: {},
      methods: {},
      directives: {},
    }
  ) {
    if (!option.mounted) {
      option.mounted = function () {};
    }
    if (!option.data) {
      option.data = {};
    }
    if (typeof option.mounted === "function") {
      this.mounted = option.mounted;
    } else {
      throw new Error("mounted must be a function");
    }
    const that = this;
    this.init = false;
    this.$refs = {};
    this.effectLog = option.effectLog || false;
    // this.data = {...option.data};
    this.watch = option.watch || {};
    this.computed = option.computed || {};
    this.methods = option.methods || {};
    for (const methodName of Object.keys(this.methods)) {
      // 判断是否为函数
      if (typeof this.methods[methodName] !== "function") {
        throw new Error("methods can only puts function");
      }
      // 绑定 this
      this.methods[methodName] = this.methods[methodName].bind(this);
    }

    this.dataEffectFnMap = new Map(); // 数据变化回调，使用第一层数据的 key 作为键值
    const dataEffectFnMap = this.dataEffectFnMap;
    for (const k in option.data) {
      const mappingSet = new Set<Function>();
      this.dataEffectFnMap.set(k, mappingSet);
    }
    const track = (key: any) => {
      if (!activeEffectFunction) {
        return;
      }
      // 为数据绑定相关 render 函数
      const relatedEffectFnSet = dataEffectFnMap.get(key);
      if (relatedEffectFnSet && !relatedEffectFnSet.has(activeEffectFunction)) {
        relatedEffectFnSet.add(activeEffectFunction);
      }
    };
    // 数据更新 trigger
    const trigger = (key: any, newValue: any, oldValue: any) => {
      // 元素更新
      const relatedEffectFnSet = dataEffectFnMap.get(key);
      if (relatedEffectFnSet) {
        for (const fn of relatedEffectFnSet) {
          fn();
        }
      }
      // watch
      if (this.watch[key]) {
        this.watch[key](newValue, oldValue);
      }
    };

    const initDataProxy = (
      一级父对象在data中的key: string | undefined, // data 下属第一层对象的 key
      存放数据的对象: object | any[] // 数据 存在的上一层； a: { b: 1 }， 为 a
    ): ProxyConstructor => {
      let data: any;
      if (Array.isArray(存放数据的对象)) {
        data = new Array(存放数据的对象.length);
      } else if (typeof 存放数据的对象 === "object") {
        data = {};
      } else {
        throw new Error("父对象类型错误");
      }

      const getDataProxy = (
        一级父对象在data中的key: string | undefined,
        被代理对象: any
      ): ProxyConstructor => {
        if (!被代理对象) {
          throw new Error("被代理对象不能为空值");
        }
        const proxy = new Proxy(被代理对象, {
          deleteProperty(target, prop) {
            const oldValue = Reflect.get(target, prop);
            Reflect.deleteProperty(target, prop);

            // 触发所有依赖该数据的函数
            const keyName =
              一级父对象在data中的key === undefined
                ? prop
                : 一级父对象在data中的key;
            trigger(keyName, undefined, oldValue);
            return true;
          },
          get(target: any, prop: string, receiver) {
            // 组件 render 时候会 get 数据从而触发
            // 追踪数据变化，为数据绑定相关 effect 函数
            const keyName =
              一级父对象在data中的key === undefined
                ? prop
                : 一级父对象在data中的key;
            track(keyName);
            if (prop === "__raw__") {
              return target;
            }
            return Reflect.get(target, prop, receiver);
          },
          set(target: any, prop: string, newValue, receiver) {
            const oldValue = Reflect.get(target, prop, receiver);
            if (oldValue === newValue) {
              return true;
            }

            const keyName =
              一级父对象在data中的key === undefined
                ? prop
                : 一级父对象在data中的key;
            if (typeof newValue === "object" || Array.isArray(newValue)) {
              const newProxy = initDataProxy(keyName, newValue);
              Reflect.set(target, prop, newProxy, receiver);
            } else {
              Reflect.set(target, prop, newValue, receiver);
            }
            if (that.effectLog) {
              console.log(
                `[${prop.toString()}]set`,
                oldValue,
                "=>",
                Reflect.get(target, prop, receiver)
              );
            }
            // 触发更新
            trigger(keyName, newValue, oldValue);
            return true;
          },
        });

        // if (Array.isArray(被代理对象)) {
        //     proxy[Symbol.iterator] = function* () {
        //         for (let i = 0; i < Object.keys(proxy).length; i++) {
        //             yield proxy[i];
        //         }
        //     };
        // }
        return proxy;
      };

      // key 可能为 index
      for (const [key, value] of Object.entries(存放数据的对象)) {
        if (typeof value === "object" || Array.isArray(value)) {
          const keyName =
            一级父对象在data中的key === undefined
              ? key.toString()
              : 一级父对象在data中的key;
          data[key] = initDataProxy(keyName, value);
        } else {
          data[key] = value;
        }
      }
      return getDataProxy(一级父对象在data中的key, data);
    };
    if (option.data) {
      this.data = initDataProxy(undefined, option.data);
    } else {
      this.data = initDataProxy(undefined, {});
    }

    // 内置指令
    // 添加 e-show 指令
    const updateElementEShow = (
      element: HTMLElement,
      property: EUVNodeProperty
    ) => {
      if (property.calculatedValue) {
        element.style.display = "";
      } else {
        element.style.display = "none";
      }
    };
    compiler.registerDirective(
      {
        name: "show",
        calculatedValue: true,
      },
      {
        mounted(element, property) {
          updateElementEShow(element, property);
        },
        updated(element, property) {
          updateElementEShow(element, property);
        },
      }
    );
    // 添加 e-bind: 和 :（冒号） 指令
    const updateElementEBing = (
      element: HTMLElement,
      property: EUVNodeProperty
    ) => {
      const arrtibuteName = property.name.split(":")[1];
      if (!arrtibuteName) {
        return;
      }

      if (
        property.calculatedValue === undefined ||
        property.calculatedValue === null
      ) {
        return;
      }
      element.setAttribute(arrtibuteName, property.calculatedValue.toString());
    };
    compiler.registerDirective(
      {
        name: "bind",
        aliasName: ":",
        calculatedValue: true,
      },
      {
        mounted(element, property) {
          updateElementEBing(element, property);
        },
        updated(element, property) {
          updateElementEBing(element, property);
        },
      }
    );
    // 添加 e-model 指令
    const eMOdelAttributeObserver = new MutationObserver((records) => {
      for (const r of records) {
        // console.log(r);
        const element: any = r.target;
        // 查看属性是否有 e-model
        const 指令属性名 = "e-model:" + r.attributeName;
        const 数据内容 = element.getAttribute(指令属性名);
        if (!数据内容) {
          continue;
        }
        try {
          const newValue = element.getAttribute(r.attributeName);
          new Function("newValue", `(this.data.${数据内容}) = newValue;`).bind(
            this
          )(newValue);
        } catch (e) {
          console.error(element, 指令属性名, "错误的值");
        }
      }
    });
    compiler.registerDirective(
      {
        name: "model",
        calculatedValue: true,
      },
      {
        mounted(element: any, property) {
          // 做数据绑定
          const 被绑定的属性的名字 = property.name.split(":")[1];
          if (!被绑定的属性的名字) {
            // 无有绑定属性
            return;
          }
          // 修改属性值
          element.setAttribute(被绑定的属性的名字, property.calculatedValue);
          // 监听属性
          eMOdelAttributeObserver.observe(element, {
            attributes: true,
          });
          const 修改data的值 =
            element[被绑定的属性的名字] !== undefined
              ? () => {
                  const newValue = element[被绑定的属性的名字];
                  new Function(
                    "newValue",
                    `(this.data.${property.value}) = newValue;`
                  ).bind(that)(newValue);
                }
              : () => {};
          element.addEventListener("change", 修改data的值);
          element.addEventListener("input", 修改data的值);
        },
        updated(element, property) {
          const 被绑定的属性的名字 = property.name.split(":")[1];
          if (!被绑定的属性的名字) {
            return;
          }
          element.setAttribute(被绑定的属性的名字, property.calculatedValue);
        },
      }
    );
    // 添加 e-on: 和 @ 指令
    compiler.registerDirective(
      {
        name: "on",
        aliasName: "@",
      },
      {
        mounted(element, property) {
          let eventName = property.name.split(":")[1];
          if (!eventName) {
            eventName = property.name.split("@")[1];
          }
          if (!eventName) {
            return;
          }

          const expression = property.value.trim();
          if (that.methods[expression]) {
            element.addEventListener(eventName, (e) => {
              that.methods[expression](e);
            });
          } else {
            throw new Error("暂时只支持使用 methods 中的函数");
          }
        },
      }
    );

    // 挂载
    if (option.elementSelector) {
      this.mount(option.elementSelector);
    }
  }

  getValuesByName(argNames: string[]): any[] {
    // 使用 标识符 从 data computed methods 中取值
    // 用于 运行 html 中的表达式
    let args = new Array(argNames.length);
    argNames.forEach((name: any, index) => {
      const dataValue = this.data[name];
      if (dataValue !== undefined) {
        args[index] = dataValue;
      } else if (this.computed[name]) {
        args[index] = this.computed[name].bind(this)();
      } else if (this.methods[name]) {
        args[index] = this.methods[name]?.bind(this)();
      } else {
        args[index] = window[name];
      }
    });
    return args;
  }

  initEUVNodeChildrenRef(node: EUVNode) {
    // 初始化 domElement ref
    const createDOMElementRef = (node: EUVNode) => {
      const refName = node.props["ref"];
      if (!refName) {
        return null;
      }
      if (!node.element) {
        return;
      }
      this.$refs[refName] = new Proxy(node.element, {
        get: (target: any, prop: string) => {
          return target[prop];
        },
        set: (target: any, prop: string, value) => {
          target[prop] = value;
          // 判断是否更新某属性
          const attributeValue = target.getAttribute(prop);
          if (attributeValue !== undefined && attributeValue !== null) {
            // 更新属性是否 e-model
            const attributeName = "e-model:" + prop;
            const 绑定的数据内容 = target.getAttribute(attributeName);
            if (绑定的数据内容) {
              target.setAttribute(prop, value);
            }
          }
          return true;
        },
      });
    };
    const initChildrenDOMElementRef = (node: EUVNode) => {
      if (!node.children) {
        return;
      }
      for (const child of node.children) {
        initChildrenDOMElementRef(child);
        createDOMElementRef(child);
      }
    };
    initChildrenDOMElementRef(node);
    createDOMElementRef(node);
  }

  /**
   * 挂载 EUV APP
   * @param { string } elementSelector
   * @returns
   */
  mount(elementSelector: string) {
    if (!elementSelector || typeof elementSelector !== "string") {
      return;
    }
    /**
     * @type { HTMLElement }
     */
    this.mountedElement = document.querySelector(elementSelector);
    if (!this.mountedElement) {
      throw new Error("elementSelector is not valid");
    }
    this.mountedElement.setAttribute("data-e-app", "");

    // 初始化元素
    this.appNode = compiler.compileDOMElement(this.mountedElement);
    if (!this.appNode) {
      throw new Error("未知错误");
    }
    compiler.compileEUVNode(
      this.appNode,
      this.mountedElement,
      (needArgs) => this.getValuesByName(needArgs)
    );
    // this.mountedElement.innerHTML = "";

    // 渲染DOM元素
    const renderNode = (node: EUVNode) => {
      if (node.children) {
        for (const child of node.children) {
          renderNode(child);
        }
      }
      if (!node.render) {
        return;
      }

      activeEffectFunction = node.render;
      node.render();
      activeEffectFunction = null;
    };
    renderNode(this.appNode);
    this.initEUVNodeChildrenRef(this.appNode);

    this.init = true;
    // 完成 mounted 动作
    if (this.mounted) this.mounted.bind(this)();
  }
}
